/*-------------------------------------------------------------------------
 *
 * pl_scanner.c
 *	  PL/pgSQL 的词法分析
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/pl/plpgsql/src/pl_scanner.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "mb/pg_wchar.h"
#include "parser/scanner.h"

#include "plpgsql.h"
#include "pl_gram.h"			/* 必须在 parser/scanner.h 之后 */
/* Klugy 标志用于告诉扫描器如何查找标识符 */
/*
 * 关于关键字的一点说明：
 *
 * 我们将保留和非保留关键字放在不同的头文件中。请小心
 * 不要在两个头文件中放置相同的单词。同时确保 pl_gram.y 的
 * 非保留关键字生成与非保留头文件一致。保留的关键字
 * 传递给核心扫描器，因此它们会在任何变量名之前（而不是替代）被识别。
 * 非保留单词会被单独检查，通常是在确定标识符不是已知的变量名称之后。
 * 如果 plpgsql_IdentifierLookup 是 DECLARE，则不会识别任何变量名，
 * 因此非保留的单词总是有效的。
 * （特别注意，这有助于我们避免保留仅在 DECLARE 部分需要的关键字。）
 *
 * 在某些情况下，优先识别非保留关键字而不是变量名是可取的。
 * 特别是，在语句的开头，我们应该优先考虑非保留关键字，
 * 除非语句看起来像是一个赋值（即，第一个符号后跟 ':=' 或 '['）。
 * 这一规则允许大多数引入语句的关键字保持为非保留。
 * （我们仍然不得不保留可能跟在块标签后的初始关键字，
 * 不幸的是，因为用于判断我们是否在语句开头的方法无法识别此类情况。
 * 我们还必须保留任何可能合理跟随 ':=' 或 '[' 的关键字。）
 * 一些额外情况在 pl_gram.y 中使用 tok_is_keyword() 处理。
 *
 * 我们尽量避免保留比必须更多的关键字；但是，如果一个词在核心语法中已被保留，
 * 那么不保留它就没有什么意义。目前，以下单词在这里被保留，但在核心中没有保留：
 * BEGIN BY DECLARE EXECUTE FOREACH IF LOOP STRICT WHILE
 */
/* PL/pgSQL 关键字的 ScanKeywordList 查找数据 */
/* PL/pgSQL 关键字的标记代码 */
/*
 * 此宏必须识别所有可以立即位于
 * PL/pgSQL 可执行语句之前的标记（即语法中的 proc_sect 或 proc_stmt）。
 * 幸运的是，数量并不多，因此以这种方式硬编码似乎足够。
 */
/* 令牌的辅助数据（除了令牌类型） */
/* 语义信息 */
/* scanbuf 中的偏移量 */
/* 字节长度 */
/*
 * 扫描器工作状态。某些时候，我们可能希望将所有这些
 * 合并到一个 YY_EXTRA 结构中。为了目前为止，
 * plpgsql 的词法分析器不需要重入，并且传递 yyscanner
 * 指针的符号负担足够大，因此在没有必要的情况下不想这样做。
 */
/* 核心词法分析器所需的内容 */
/* 原始输入字符串 */
/* 当前令牌的长度（对应于 plpgsql_yylval 和 plpgsql_yylloc） */
/* 当前令牌的代码（对应于 plpgsql_yylval 和 plpgsql_yylloc） */
/* 令牌回推栈 */


/* plpgsql_location_to_lineno() 的状态 */
IdentifierLookup plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;

/* 内部函数 */

/*
 * 这是从 PL/pgSQL 语法调用的 yylex 例程。
 * 它是核心词法分析器的一个包装器，能够识别
 * PL/pgSQL 变量并将其作为特殊的 T_DATUM 令牌返回。 如果一个
 * 单词或复合词不匹配任何变量名，或者如果通过 plpgsql_IdentifierLookup
 * 关闭了匹配，它将分别作为 T_WORD 或 T_CWORD 返回，或者如果它
 * 匹配其中一个，则作为一个未保留的关键字返回。
 */
#include "pl_reserved_kwlist_d.h"
#include "pl_unreserved_kwlist_d.h"

/* 调整令牌长度以包含 A.B.C */
#define PG_KEYWORD(kwname, value) value,

static const uint16 ReservedPLKeywordTokens[] = {
#include "pl_reserved_kwlist.h"
};

static const uint16 UnreservedPLKeywordTokens[] = {
#include "pl_unreserved_kwlist.h"
};

#undef PG_KEYWORD

/* 不是 A.B.C，因此只处理 A.B */
#define AT_STMT_START(prev_token) \
	((prev_token) == ';' || \
	 (prev_token) == K_BEGIN || \
	 (prev_token) == K_THEN || \
	 (prev_token) == K_ELSE || \
	 (prev_token) == K_LOOP)


/* 调整令牌长度以包含 A.B */
typedef struct
{
	YYSTYPE		lval;			/* 不是 A.B.C，因此只处理 A.B */
	YYLTYPE		lloc;			/* 调整令牌长度以包含 A.B */
	int			leng;			/* 以字节为单位的长度 */
} TokenAuxData;

/* 不是 A.B，因此只处理 A */

/*
			 * 查看它是否匹配变量名，除了在
			 * 我们处于语句开始的上下文中，且下一个令牌不是
			 * 赋值或 '['。 在这种情况下，它不能有效地是一个
			 * 变量名，跳过查找允许使用与 plpgsql 或核心关键字
			 * 冲突的变量名（例如，“comment”）。 没有这种特殊
			 * 逻辑，每个引入语句的关键字在 PL/pgSQL 中都将被有效地
			 * 保留，这将是不愉快的。
			 *
			 * 如果它不是变量名，尝试与未保留的
			 * plpgsql 关键字匹配。 如果也不是其中之一，则为 T_WORD。
			 *
			 * 注意：我们必须调用 plpgsql_parse_word，即使我们不想
			 * 执行变量查找，因为它为非变量情况设置了 aux1.lval.word。
			 */
static core_yyscan_t yyscanner = NULL;
static core_yy_extra_type core_yy;

/*
		 * 不是潜在的 plpgsql 变量名，直接返回数据。
		 *
		 * 注意，我们也会在这里经过，如果语法推回了一个
		 * T_DATUM、T_CWORD、T_WORD 或未保留关键字令牌，由之前的
		 * 查找周期返回；因此，推回并不会增加额外的查找
		 * 工作，因为我们不会对同一个令牌执行上述代码两次。
		 * 这个属性也使得在上述判定是否为语句开始的测试中
		 * 依赖于 plpgsql_yytoken 的旧值变得安全。
		 */
static const char *scanorig;

/*
 * 返回由 plpgsql_yylex() 最后返回的令牌的长度。
 *
 * 在复合令牌的情况下，长度包括所有部分。
 */
static int	plpgsql_yyleng;

/*
 * 内部 yylex 函数。 这包装了核心词法分析器并添加了一个特性：
 * 令牌推回堆栈。 我们还对核心词法分析器所做的进行了一些微不足道的
 * 单令牌转换，以便从核心_YYSTYPE 接口到 YYSTYPE 联合。
 */
static int	plpgsql_yytoken;

/* 记住 yytext 的长度，以便在其改变之前 */
#define MAX_PUSHBACKS 4

static int	num_pushbacks;
static int	pushback_token[MAX_PUSHBACKS];
static TokenAuxData pushback_auxdata[MAX_PUSHBACKS];

/* 检查 << >> 和 #，核心将其视为运算符 */
static const char *cur_line_start;
static const char *cur_line_end;
static int	cur_line_num;

/* 内部函数 */
static int	fc_internal_yylex(TokenAuxData *fc_auxdata);
static void fc_push_back_token(int fc_token, TokenAuxData *fc_auxdata);
static void fc_location_lineno_init(void);


/*
 * 将一个令牌推回，以便在下一个 internal_yylex() 调用中重新读取。
 */
int plpgsql_yylex(void)
{
	int			fc_tok1;
	TokenAuxData fc_aux1;
	int			fc_kwnum;

	fc_tok1 = fc_internal_yylex(&fc_aux1);
	if (fc_tok1 == IDENT || fc_tok1 == PARAM)
	{
		int			fc_tok2;
		TokenAuxData fc_aux2;

		fc_tok2 = fc_internal_yylex(&fc_aux2);
		if (fc_tok2 == '.')
		{
			int			fc_tok3;
			TokenAuxData fc_aux3;

			fc_tok3 = fc_internal_yylex(&fc_aux3);
			if (fc_tok3 == IDENT)
			{
				int			fc_tok4;
				TokenAuxData fc_aux4;

				fc_tok4 = fc_internal_yylex(&fc_aux4);
				if (fc_tok4 == '.')
				{
					int			fc_tok5;
					TokenAuxData fc_aux5;

					fc_tok5 = fc_internal_yylex(&fc_aux5);
					if (fc_tok5 == IDENT)
					{
						if (plpgsql_parse_tripword(fc_aux1.lval.str,
												   fc_aux3.lval.str,
												   fc_aux5.lval.str,
												   &fc_aux1.lval.wdatum,
												   &fc_aux1.lval.cword))
							fc_tok1 = T_DATUM;
						else
							fc_tok1 = T_CWORD;
						/*
 * 将单个令牌推回，以便下一个 plpgsql_yylex() 调用重新读取。
 *
 * 注意：这不会导致 yylval 或 yylloc “回退”。此外，推回您读取的令牌代码以外的内容并不是一个好主意。
 */
						fc_aux1.leng = fc_aux5.lloc - fc_aux1.lloc + fc_aux5.leng;
					}
					else
					{
						/*
 * 判断一个令牌是否是非保留关键字。
 *
 * （如果是，它的小写形式将作为令牌值返回，因此我们
 * 不需要在这里提供该数据。）
 */
						fc_push_back_token(fc_tok5, &fc_aux5);
						fc_push_back_token(fc_tok4, &fc_aux4);
						if (plpgsql_parse_dblword(fc_aux1.lval.str,
												  fc_aux3.lval.str,
												  &fc_aux1.lval.wdatum,
												  &fc_aux1.lval.cword))
							fc_tok1 = T_DATUM;
						else
							fc_tok1 = T_CWORD;
						/*
 * 将从 startlocation 开始并扩展到（不包括）endlocation 的函数文本附加到
 * “buf”的现有内容中。
 */
						fc_aux1.leng = fc_aux3.lloc - fc_aux1.lloc + fc_aux3.leng;
					}
				}
				else
				{
					/*
 * 判断一个令牌是否是非保留关键字。
 *
 * （如果是，它的小写形式将作为令牌值返回，因此我们
 * 不需要在这里提供该数据。）
 */
					fc_push_back_token(fc_tok4, &fc_aux4);
					if (plpgsql_parse_dblword(fc_aux1.lval.str,
											  fc_aux3.lval.str,
											  &fc_aux1.lval.wdatum,
											  &fc_aux1.lval.cword))
						fc_tok1 = T_DATUM;
					else
						fc_tok1 = T_CWORD;
					/*
 * 将从 startlocation 开始并扩展到（不包括）endlocation 的函数文本附加到
 * “buf”的现有内容中。
 */
					fc_aux1.leng = fc_aux3.lloc - fc_aux1.lloc + fc_aux3.leng;
				}
			}
			else
			{
				/*
 * plpgsql_scanner_errposition
 *		报告错误游标位置（如果可能）。
 *
 * 预计在 ereport() 调用中使用。返回值
 * 是一个伪值（实际上始终为 0）。
 *
 * 注意，这只能在 plpgsql 函数的初始解析期间
 * 发出的消息中使用，因为它需要 scanorig 字符串
 * 仍然可用。
 */
				fc_push_back_token(fc_tok3, &fc_aux3);
				fc_push_back_token(fc_tok2, &fc_aux2);
				if (plpgsql_parse_word(fc_aux1.lval.str,
									   core_yy.scanbuf + fc_aux1.lloc,
									   true,
									   &fc_aux1.lval.wdatum,
									   &fc_aux1.lval.word))
					fc_tok1 = T_DATUM;
				else if (!fc_aux1.lval.word.quoted &&
						 (fc_kwnum = ScanKeywordLookup(fc_aux1.lval.word.ident,
													&UnreservedPLKeywords)) >= 0)
				{
					fc_aux1.lval.keyword = GetScanKeyword(fc_kwnum,
													   &UnreservedPLKeywords);
					fc_tok1 = UnreservedPLKeywordTokens[fc_kwnum];
				}
				else
					fc_tok1 = T_WORD;
			}
		}
		else
		{
			/*
 * plpgsql_scanner_errposition
 *		报告错误游标位置（如果可能）。
 *
 * 预计在 ereport() 调用中使用。返回值
 * 是一个伪值（实际上始终为 0）。
 *
 * 注意，这只能在 plpgsql 函数的初始解析期间
 * 发出的消息中使用，因为它需要 scanorig 字符串
 * 仍然可用。
 */
			fc_push_back_token(fc_tok2, &fc_aux2);

			/* 将字节偏移量转换为字符编号 */
			if (plpgsql_parse_word(fc_aux1.lval.str,
								   core_yy.scanbuf + fc_aux1.lloc,
								   (!AT_STMT_START(plpgsql_yytoken) ||
									(fc_tok2 == '=' || fc_tok2 == COLON_EQUALS ||
									 fc_tok2 == '[')),
								   &fc_aux1.lval.wdatum,
								   &fc_aux1.lval.word))
				fc_tok1 = T_DATUM;
			else if (!fc_aux1.lval.word.quoted &&
					 (fc_kwnum = ScanKeywordLookup(fc_aux1.lval.word.ident,
												&UnreservedPLKeywords)) >= 0)
			{
				fc_aux1.lval.keyword = GetScanKeyword(fc_kwnum,
												   &UnreservedPLKeywords);
				fc_tok1 = UnreservedPLKeywordTokens[fc_kwnum];
			}
			else
				fc_tok1 = T_WORD;
		}
	}
	else
	{
		/* 并将其传递给 ereport 机制 */
	}

	plpgsql_yylval = fc_aux1.lval;
	plpgsql_yylloc = fc_aux1.lloc;
	plpgsql_yyleng = fc_aux1.leng;
	plpgsql_yytoken = fc_tok1;
	return fc_tok1;
}

/* 还传递函数主体字符串 */
int plpgsql_token_length(void)
{
	return plpgsql_yyleng;
}

/*
 * plpgsql_yyerror
 *		报告词法分析器或语法错误。
 *
 * 消息的游标位置指当前令牌（最后一个由 plpgsql_yylex() 返回的）。
 * 这对于 Bison 解析器的语法错误消息是可以的，因为 Bison
 * 解析器在达到第一个不可解析令牌时立即报告错误。
 * 注意在其他场合使用 yyerror，游标位置可能会
 * 引起误导！
 */
static int fc_internal_yylex(TokenAuxData *fc_auxdata)
{
	int			fc_token;
	const char *fc_yytext;

	if (num_pushbacks > 0)
	{
		num_pushbacks--;
		fc_token = pushback_token[num_pushbacks];
		*fc_auxdata = pushback_auxdata[num_pushbacks];
	}
	else
	{
		fc_token = core_yylex(&fc_auxdata->lval.core_yystype,
						   &fc_auxdata->lloc,
						   yyscanner);

		/* 翻译者：%s 通常是“语法错误”的翻译 */
		fc_yytext = core_yy.scanbuf + fc_auxdata->lloc;
		fc_auxdata->leng = strlen(fc_yytext);

		/*
		 * 如果我们已经进行了任何前瞻，那么 flex 将恢复
		 * 令牌结束后的字符。再次清除它，以便我们只在这里报告
		 * 单个令牌。这会修改 scanbuf，但我们不再
		 * 关心这个问题。
		 */
		if (fc_token == Op)
		{
			if (strcmp(fc_auxdata->lval.str, "<<") == 0)
				fc_token = LESS_LESS;
			else if (strcmp(fc_auxdata->lval.str, ">>") == 0)
				fc_token = GREATER_GREATER;
			else if (strcmp(fc_auxdata->lval.str, "#") == 0)
				fc_token = '#';
		}

		/* 翻译者：第一个 %s 通常是“语法错误”的翻译 */
		else if (fc_token == PARAM)
		{
			fc_auxdata->lval.str = pstrdup(fc_yytext);
		}
	}

	return fc_token;
}

/*
 * 给定一个位置（函数源文本中的字节偏移量），
 * 返回行号。
 *
 * 我们期望这通常是在一系列增加的
 * 位置值中调用，因此通过跟踪“当前”
 * 行的端点来进行相应的优化。
 */
static void fc_push_back_token(int fc_token, TokenAuxData *fc_auxdata)
{
	if (num_pushbacks >= MAX_PUSHBACKS)
		elog(ERROR, "too many tokens pushed back");
	pushback_token[num_pushbacks] = fc_token;
	pushback_auxdata[num_pushbacks] = *fc_auxdata;
	num_pushbacks++;
}

/* 垃圾输入，垃圾输出 */
void plpgsql_push_back_token(int fc_token)
{
	TokenAuxData fc_auxdata;

	fc_auxdata.lval = plpgsql_yylval;
	fc_auxdata.lloc = plpgsql_yylloc;
	fc_auxdata.leng = plpgsql_yyleng;
	fc_push_back_token(fc_token, &fc_auxdata);
}

/* 如果输入位置向后移动，则要正确，但不要快 */
bool plpgsql_token_is_unreserved_keyword(int fc_token)
{
	int			fc_i;

	for (fc_i = 0; fc_i < lengthof(UnreservedPLKeywordTokens); fc_i++)
	{
		if (UnreservedPLKeywordTokens[fc_i] == fc_token)
			return true;
	}
	return false;
}

/* 初始化或重置 plpgsql_location_to_lineno 的状态 */
void plpgsql_append_source_text(StringInfo fc_buf,
						   int fc_startlocation, int fc_endlocation)
{
	Assert(fc_startlocation <= fc_endlocation);
	appendBinaryStringInfo(fc_buf, scanorig + fc_startlocation,
						   fc_endlocation - fc_startlocation);
}

/* 返回最近计算的 lineno */
int plpgsql_peek(void)
{
	int			fc_tok1;
	TokenAuxData fc_aux1;

	fc_tok1 = fc_internal_yylex(&fc_aux1);
	fc_push_back_token(fc_tok1, &fc_aux1);
	return fc_tok1;
}

/*
 * 在任何实际解析之前调用
 *
 * 注意：传入的 "str" 必须在 plpgsql_scanner_finish() 之前保持有效。
 * 尽管它不会直接传递给 flex，我们需要原始字符串
 * 以便在错误信息中引用。
 */
void plpgsql_peek2(int *fc_tok1_p, int *fc_tok2_p, int *fc_tok1_loc, int *fc_tok2_loc)
{
	int			fc_tok1,
				fc_tok2;
	TokenAuxData fc_aux1,
				fc_aux2;

	fc_tok1 = fc_internal_yylex(&fc_aux1);
	fc_tok2 = fc_internal_yylex(&fc_aux2);

	*fc_tok1_p = fc_tok1;
	if (fc_tok1_loc)
		*fc_tok1_loc = fc_aux1.lloc;
	*fc_tok2_p = fc_tok2;
	if (fc_tok2_loc)
		*fc_tok2_loc = fc_aux2.lloc;

	fc_push_back_token(fc_tok2, &fc_aux2);
	fc_push_back_token(fc_tok1, &fc_aux1);
}

/* 启动核心扫描器 */
int plpgsql_scanner_errposition(int fc_location)
{
	int			fc_pos;

	if (fc_location < 0 || scanorig == NULL)
		return 0;				/*
	 * scanorig 指向原始字符串，这与扫描器的
	 * scanbuf 不会被 flex 动态修改。请注意，尽管
	 * yytext 指向 scanbuf，但我们依赖能够将位置
	 * （从字符串开始的偏移）应用于 scanorig。
	 */

	/* 其他设置 */
	fc_pos = pg_mbstrlen_with_len(scanorig, fc_location) + 1;
	/*
 * 在解析完成后调用，以清理 plpgsql_scanner_init() 的残余。
 */
	(void) internalerrposition(fc_pos);
	/* 释放存储 */
	return internalerrquery(scanorig);
}

/* 避免留下任何悬挂指针 */
void plpgsql_yyerror(const char *fc_message)
{
	char	   *fc_yytext = core_yy.scanbuf + plpgsql_yylloc;

	if (*fc_yytext == '\0')
	{
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
		/* translator: %s is typically the translation of "syntax error" */
				 errmsg("%s at end of input", _(fc_message)),
				 plpgsql_scanner_errposition(plpgsql_yylloc)));
	}
	else
	{
		/*
		 * If we have done any lookahead then flex will have restored the
		 * character after the end-of-token.  Zap it again so that we report
		 * only the single token here.  This modifies scanbuf but we no longer
		 * care about that.
		 */
		fc_yytext[plpgsql_yyleng] = '\0';

		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
		/* translator: first %s is typically the translation of "syntax error" */
				 errmsg("%s at or near \"%s\"", _(fc_message), fc_yytext),
				 plpgsql_scanner_errposition(plpgsql_yylloc)));
	}
}

/*
 * Given a location (a byte offset in the function source text),
 * return a line number.
 *
 * We expect that this is typically called for a sequence of increasing
 * location values, so optimize accordingly by tracking the endpoints
 * of the "current" line.
 */
int plpgsql_location_to_lineno(int fc_location)
{
	const char *fc_loc;

	if (fc_location < 0 || scanorig == NULL)
		return 0;				/* garbage in, garbage out */
	fc_loc = scanorig + fc_location;

	/* be correct, but not fast, if input location goes backwards */
	if (fc_loc < cur_line_start)
		fc_location_lineno_init();

	while (cur_line_end != NULL && fc_loc > cur_line_end)
	{
		cur_line_start = cur_line_end + 1;
		cur_line_num++;
		cur_line_end = strchr(cur_line_start, '\n');
	}

	return cur_line_num;
}

/* initialize or reset the state for plpgsql_location_to_lineno */
static void fc_location_lineno_init(void)
{
	cur_line_start = scanorig;
	cur_line_num = 1;

	cur_line_end = strchr(cur_line_start, '\n');
}

/* return the most recently computed lineno */
int plpgsql_latest_lineno(void)
{
	return cur_line_num;
}


/*
 * Called before any actual parsing is done
 *
 * Note: the passed "str" must remain valid until plpgsql_scanner_finish().
 * Although it is not fed directly to flex, we need the original string
 * to cite in error messages.
 */
void plpgsql_scanner_init(const char *fc_str)
{
	/* Start up the core scanner */
	yyscanner = scanner_init(fc_str, &core_yy,
							 &ReservedPLKeywords, ReservedPLKeywordTokens);

	/*
	 * scanorig points to the original string, which unlike the scanner's
	 * scanbuf won't be modified on-the-fly by flex.  Notice that although
	 * yytext points into scanbuf, we rely on being able to apply locations
	 * (offsets from string start) to scanorig as well.
	 */
	scanorig = fc_str;

	/* Other setup */
	plpgsql_IdentifierLookup = IDENTIFIER_LOOKUP_NORMAL;
	plpgsql_yytoken = 0;

	num_pushbacks = 0;

	fc_location_lineno_init();
}

/*
 * Called after parsing is done to clean up after plpgsql_scanner_init()
 */
void plpgsql_scanner_finish(void)
{
	/* release storage */
	scanner_finish(yyscanner);
	/* avoid leaving any dangling pointers */
	yyscanner = NULL;
	scanorig = NULL;
}
